package com.atguigu.tingshu.common.login.aspect;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.constant.PublicConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.login.annotation.TingshuLogin;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * 登录切面类，用于处理登录相关的逻辑
 */
@Component
@Aspect
public class LoginAspect {

    // 注入Redis模板，用于操作Redis数据库
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 环绕通知方法，用于检查用户登录状态并执行目标方法
     * @param pjp 切入点对象，包含目标方法的信息
     * @return 目标方法的执行结果
     * @throws Throwable 目标方法抛出的异常
     */
    @Around(value = "@annotation(com.atguigu.tingshu.common.login.annotation.TingshuLogin)")
    public Object checkLogin(ProceedingJoinPoint pjp) throws Throwable {
        // 前置通知
        // 0.获取前端请求中请求头的token值
        String accessToken = getAccessTokenFromFront();



        // 1. 判断登录注解的required属性是否为true或者false
        Boolean isLogin = getLoginSwitch(pjp); // 限制接口，不是限制用户

        if (!isLogin) {
            if (StringUtils.isEmpty(accessToken)) {
                return  pjp.proceed();// 放行进到目标方法
            }
        }
        // 2.校验accessToken && 获取用户id
        Long userId = checkAccessTokenAndGetUserId(accessToken);

        // 将用户id设置到上下文中，以便后续使用
        AuthContextHolder.setUserId(userId);
        Object retVal;  // 执行目标方法
        try {
            retVal = pjp.proceed();
        } finally {
            // 把存放到ThreadLocal中的value移除掉。
            AuthContextHolder.removeUserId();
        }
        // 后置通知
        return retVal;
    }
    private Boolean getLoginSwitch(ProceedingJoinPoint pjp) {

        // 1.获取方法签名对象
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();

        // 2.获取方法对象
        Method method = methodSignature.getMethod();

        // 3.获取方法上的注解对象
        TingshuLogin annotation = method.getAnnotation(TingshuLogin.class);

        // 4.获取注解的属性值
        return annotation.required();
    }

    /**
     * 校验accessToken并获取用户id
     * @param accessToken 前端请求中携带的token
     * @return 用户id
     * @throws GuiguException 如果token校验失败或已过期
     */
    private Long checkAccessTokenAndGetUserId(String accessToken) {
        // 1. 校验前端是否携带accessToken
        if (StringUtils.isEmpty(accessToken)) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        // 2.校验accessToken是否携带对了（1、accessToken是否被篡改过 2、accessToken是否和服务端存的是同一个）
        // 2.1 通过jwt验签来判断（只要验签通过，一定没有被篡改）
        Jwt jwt = JwtHelper.decodeAndVerify(accessToken, new RsaVerifier(PublicConstant.PUBLIC_KEY));

        // 解析jwt获取用户信息
        String claims = jwt.getClaims();
        Map map = JSONObject.parseObject(claims, Map.class);
        String openId = (String) map.get("openId");
        Object userId = map.get("userId");

        // 2.2 accessToken是否和服务端(redis)存的是同一个
        String accessTokenKey = RedisConstant.USER_LOGIN_KEY_PREFIX + openId;
        String fromRedisJwt = redisTemplate.opsForValue().get(accessTokenKey);
        if (StringUtils.isEmpty(fromRedisJwt) || !accessToken.equals(fromRedisJwt)) {
            throw new GuiguException(401, "accessToken过期了");
        }
        // 2.3 直接返回用户id
        return Long.parseLong(userId + "");
    }

    /**
     * 从前端请求中获取accessToken
     * @return 请求头中的token值
     */
    private String getAccessTokenFromFront() {
        // 1.获取请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        // 2.获取请求对象
        HttpServletRequest request = null;
        if (requestAttributes != null) {
            request = requestAttributes.getRequest();
        }

        // 3.获取请求头的token值
        if (request != null) {
            return request.getHeader("token");
        }
        return null;
    }
}
